#include <QTimer>
#include <QDateTime>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QThread>
#include <QFile>
#include <QDataStream>
#include <QDebug>
#include <mutex>
#include <QTextCodec>

#include "serialthread.h"

#define FIREFRAME_HEAD (12)

static uint16_t crc16_ccitt_table[] = { 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a,
    0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b,
    0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 0x8528,
    0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719,
    0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 0xc9cc, 0xd9ed, 0xe98e,
    0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, 0xfbbf,
    0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec,
    0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 0xff9f, 0xefbe, 0xdfdd,
    0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1, 0x30c2,
    0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1, 0x1290, 0x22f3,
    0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 0x34e2, 0x24c3, 0x14a0,
    0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691,
    0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806,
    0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75, 0x5a54, 0x6a37,
    0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64,
    0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55,
    0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
};

static const uint16_t crc_table[16] = {
    0x0000, 0x1021, 0x2042, 0x3063,
    0x4084, 0x50a5, 0x60c6, 0x70e7,
    0x8108, 0x9129, 0xa14a, 0xb16b,
    0xc18c, 0xd1ad, 0xe1ce, 0xf1ef
};

uint16_t SerialThread::reentrent_crc_16(uint8_t *data, int32_t len, uint8_t flag)
{
    uint16_t crc16 = 0x0000;
    uint16_t crc_h4, crc_l12;
    uint8_t tmp;

    if(flag == CCITT_1) crc16 = 0xffff;

    while( len-- ) {
        crc_h4 = (crc16 >> 12);
        crc_l12 = (crc16 << 4);
        crc16 = crc_l12 ^ crc_table[crc_h4 ^ (*data >> 4)];


        crc_h4 = (crc16 >> 12);
        crc_l12 = (crc16 << 4);
        crc16 = crc_l12 ^ crc_table[crc_h4 ^ (*data & 0x0f)];

        data++;
    }

    return crc16;
}

uint16_t SerialThread::inverse_crc_16(uint8_t *data, int32_t len, uint8_t flag)
{
    uint16_t crcVal = 0x0000;

    if(flag == CCITT_1) crcVal = 0xffff;

    for(int i = 0; i < len; i++) {
        crcVal = (crcVal >> 8) ^ crc16_ccitt_table[((crcVal & 0xff) ^ data[i]) & 0xff];  //CCITT 表逆序
        //crcVal = (crcVal << 8) ^ crc16_ccitt_table[((crcVal >> 8) ^ data[i]) & 0xff];  //CCITT
    }

    return crcVal;
}

bool SerialThread::inverse_crc_check(uint8_t *data, int32_t len, uint8_t flag)
{
    if(len <= 2) return false;

    uint16_t crcVal = 0x0000;

    if(flag == CCITT_1) crcVal = 0xffff;

    for(int i = 0; i < len - 2; i++) {
        crcVal = (crcVal >> 8) ^ crc16_ccitt_table[((crcVal & 0xff) ^ data[i]) & 0xff];  //CCITT 表逆序
        //crcVal = (crcVal << 8) ^ crc16_ccitt_table[((crcVal >> 8) ^ data[i]) & 0xff];  //CCITT
    }

    uint16_t check = (data[len - 2] << 8) + data[len - 1];

    return crcVal == check;
}

SerialThread::SerialThread(QObject *parent) : QObject(parent),
    m_PortInfo(nullptr)
{
    m_Timer = new QTimer();
    m_Timer->setInterval(10);
    m_Thread = new QThread();
    m_Port = new QSerialPort();

    m_Port->setBaudRate(QSerialPort::Baud115200);
    m_Port->setDataBits(QSerialPort::Data8);
    m_Port->setParity(QSerialPort::NoParity);
    m_Port->setStopBits(QSerialPort::OneStop);
    m_Port->setFlowControl(QSerialPort::NoFlowControl);

    this->moveToThread(m_Thread);
    m_Timer->moveToThread(m_Thread);
    m_Port->moveToThread(m_Thread);

    connect(m_Thread, &QThread::destroyed, m_Thread, &QThread::deleteLater);
    connect(m_Port, &QSerialPort::errorOccurred, this, &SerialThread::SlotPortError);

    connect(m_Timer, SIGNAL(timeout()), this, SLOT(SlotTimer()));
    connect(m_Thread, SIGNAL(started()), m_Timer, SLOT(start()));
    connect(m_Thread, SIGNAL(finished()), m_Timer, SLOT(stop()));

    connect(m_Port, &QSerialPort::readyRead, this, &SerialThread::SlotRead);

    m_Thread->start();

    memset(fireSendBuf, 0, sizeof(fireSendBuf));
    fireSendBuf[0]  = 0xA5;
    fireSendBuf[1]  = 0x02;
    fireSendBuf[10] = 0xff;
}

SerialThread::~SerialThread()
{
    if(m_Thread) {
        m_Thread->quit();
        m_Thread->wait();
    }

    disconnect(m_Timer, SIGNAL(timeout()), this, SLOT(SlotTimer()));
    disconnect(m_Port, &QSerialPort::readyRead, this, &SerialThread::SlotRead);

    delete m_Thread;
    delete m_Timer;
    delete m_Port;
}

SerialThread* SerialThread::Instance()
{
    static SerialThread thread;

    return &thread;
}

bool SerialThread::IsOpen()
{
    return m_IsOpen;
}

bool SerialThread::IsUpdate()
{
    return m_IsUpdate;
}

void SerialThread::SlotSerialOpen(const QString& port, unsigned int baud)
{
    if(m_IsOpen) {
        ClosePort();
    }

    m_PortInfo = new QSerialPortInfo(port);
    m_Port->setPort(*m_PortInfo);
    m_Port->setBaudRate(baud);

    m_IsOpen = m_Port->open(QIODevice::ReadWrite);
    if(!m_IsOpen) ClosePort();
}

void SerialThread::SlotSerialClose()
{
    ClosePort();
}

void SerialThread::ClosePort()
{
    if(m_Port->isOpen()) {
        m_Port->close();
    }
    m_IsOpen = false;
    m_IsUpdate = false;

    if(m_PortInfo) {
        delete m_PortInfo;
        m_PortInfo = nullptr;
    }
}

void SerialThread::SlotTimer()
{
    static int prograssCnt = 0;
    static int delayCnt = 0;

    if(!m_IsOpen) return;

    if(fireSendState) {
        int ttick = 50000;
        if(sndfileSize >= mFirewareLen) ttick = 10000;
        if(++overTick >= ttick) {
            overTick = 0;
            write(fireSendBuf, fireSendLen);
            if(++overNum >= 3) {
                UpdateTips(u8"固件升级超时退出", 1);
            }
        }
        delayCnt = 10;
    }
    else {
        if(delayCnt > 0) delayCnt--;
    }
    if(delayCnt > 0) return;

    if(m_IsUpdate) {
        char *pBin = mFireware.data();
        quint16 crcVal, crcFrame;
        quint32 sndLen = FIREFRAME_HEAD - 1;
        QString tmp;

        switch(mUpdateState)
        {
        case 0:
            crcVal = inverse_crc_16(reinterpret_cast<uint8_t *>(pBin), mFirewareLen);
            qDebug() << "fireware crc is " << crcVal;

            tmp = QString("SND:固件校验值: %1,固件字节数: %2,共%3包")
                    .arg(crcVal, 4, 16, QChar('0'))
                    .arg(mFirewareLen)
                    .arg(mFirewareLen/32 + (mFirewareLen % 32 > 0));
            writeLog(tmp);

            fireSendBuf[sndLen++] = 0x09;
            fireSendBuf[sndLen++] = 0x01;
            fireSendBuf[sndLen++] = static_cast<quint8>(mFirewareLen >> 24);
            fireSendBuf[sndLen++] = static_cast<quint8>(mFirewareLen >> 16);
            fireSendBuf[sndLen++] = static_cast<quint8>(mFirewareLen >> 8);
            fireSendBuf[sndLen++] = static_cast<quint8>(mFirewareLen);
            fireSendBuf[sndLen++] = static_cast<quint8>(crcVal >> 8);
            fireSendBuf[sndLen++] = static_cast<quint8>(crcVal);
            crcFrame = inverse_crc_16(fireSendBuf + 12, sndLen - 12);
            fireSendBuf[sndLen++] = static_cast<quint8>(crcFrame >> 8);
            fireSendBuf[sndLen++] = static_cast<quint8>(crcFrame);

            write(fireSendBuf, sndLen);
            fireSendState = 1;
            fireSendLen = sndLen;
            overTick = 0;
            overNum = 0;
            sndfileSize = 0;

            prograssCnt = 1;
            emit SignalUpdateProgress(prograssCnt);
            break;

        case 1:
            if(mFirewareLen >= sndfileSize + 32) {
                fireSendBuf[sndLen++] = 35;
                fireSendBuf[sndLen++] = 0x02;
                memcpy(fireSendBuf + sndLen, pBin + sndfileSize, 32);
                sndLen += 32;
                sndfileSize += 32;
            }
            else {
                fireSendBuf[sndLen++] = (mFirewareLen - sndfileSize) + 3;
                fireSendBuf[sndLen++] = 0x02;
                memcpy(fireSendBuf + sndLen, pBin + sndfileSize, mFirewareLen - sndfileSize);
                sndLen += (mFirewareLen - sndfileSize);
                sndfileSize += 32;
            }
            crcFrame = inverse_crc_16(fireSendBuf + 12, sndLen - 12);
            fireSendBuf[sndLen++] = static_cast<quint8>(crcFrame >> 8);
            fireSendBuf[sndLen++] = static_cast<quint8>(crcFrame);
            tmp = QString("SND:第%1包, 包长:%2")
                    .arg(sndfileSize/32)
                    .arg(sndLen - 15);
            writeLog(tmp);
            write(fireSendBuf, sndLen);
            prograssCnt++;
            emit SignalUpdateProgress(prograssCnt);

            fireSendState = 1;
            fireSendLen = sndLen;
            overTick = 0;
            overNum = 0;
            break;
        }
    }
}

unsigned char SerialThread::HexToChar(unsigned char bChar)
{
    if((bChar>=0x30)&&(bChar<=0x39))
    {
        bChar -= 0x30;
    }
    else if((bChar>=0x41)&&(bChar<=0x46)) // Capital
    {
        bChar -= 0x37;
    }
    else if((bChar>=0x61)&&(bChar<=0x66)) //littlecase
    {
        bChar -= 0x57;
    }
    else
    {
        bChar = 0;
    }
    return bChar;
}

void SerialThread::SlotRead()
{
    QByteArray readByte = m_Port->readAll();

    if(readByte.isEmpty()) return;

    char *data = readByte.data();
    int len = readByte.length();

    writeLog("RCV", (unsigned char*)data, len);

    if(!m_IsUpdate) return;
    if(len > 64) len = 64;
    if(len + fireRecvLen >= 64) {
        memcpy(fireRecvBuf, (quint8 *)data, len);
        fireRecvLen = len;
    }
    else {
        memcpy(fireRecvBuf + fireRecvLen, (quint8 *)data, len);
        fireRecvLen += len;
    }

    if(fireRecvLen < FIREFRAME_HEAD + 3) return;

    int i = 0;
    for(; i < fireRecvLen; i++) {
        if(fireRecvBuf[i] == 0xA5
            && fireRecvBuf[i+1] == 0x02
            && fireRecvBuf[i+10] == 0xFF) break;
    }
    if(i > 0) {
        memmove(fireRecvBuf, fireRecvBuf + i, fireRecvLen - i);
        fireRecvLen -= i;
    }
    if(fireRecvLen < FIREFRAME_HEAD + 3) return;

    quint8 dlen = fireRecvBuf[FIREFRAME_HEAD - 1];
    if(dlen + FIREFRAME_HEAD <= fireRecvLen) {
        memmove(fireRecvBuf, fireRecvBuf + FIREFRAME_HEAD, fireRecvLen - FIREFRAME_HEAD);
        fireRecvLen -= FIREFRAME_HEAD;

        if(inverse_crc_check(fireRecvBuf, dlen)) {
            switch(fireRecvBuf[0]) {
            case 0x01:
                if(fireRecvBuf[1] == 0) {
                    if(mUpdateState == 0) {
                        fireSendState = 0;
                        mUpdateState = 1;
                    }
                    else {
                        UpdateTips(u8"当前固件正在发送数据包,固件申请无效", 2);
                    }
                }
                else {
                    UpdateTips(u8"当前固件申请被拒绝", 3);
                }
                break;

            case 0x02:
                if(fireRecvBuf[1] == 0) {
                    if(mUpdateState != 1) {
                        UpdateTips(u8"数据包发送中, 升级请求类型不匹配", 4);
                    }
                    else {
                        if(sndfileSize < mFirewareLen) {
                            fireSendState = 0;
                        }
                        else {                                                
                            qDebug() << "fireware sended end!";
                        }                      
                    }
                }
                else {
                    writeLog("SND", "重发固件包");
                    overTick = 0;
                    overNum = 0;
                    write(fireSendBuf, fireSendLen);
                }
                break;

            case 0x03:
                if(fireRecvBuf[1] == 0) {
                    UpdateTips(u8"升级成功", 0);
                }
                else {
                    UpdateTips(u8"升级失败", 5);
                }
                break;

            case 0x04:
                UpdateTips(u8"升级终止", 6);
                break;
            }
        }

        memmove(fireRecvBuf, fireRecvBuf + dlen, fireRecvLen - dlen);
        fireRecvLen -= dlen;
    }
}

void SerialThread::SlotPortError(QSerialPort::SerialPortError error)
{
    if(error != QSerialPort::NoError) {
        emit SignalSerialClose();
        ClosePort();
    }
}

void SerialThread::write(const unsigned char* buf, unsigned int len)
{
    if(m_IsOpen) {
        m_Port->write((char*)buf, len);
        QByteArray tmp = QByteArray((char *)buf, len);
        QString tmp1 = "SND:" + byteArrayToHexStr(tmp);
        writeLog(tmp1);
    }
}

void SerialThread::SlotUpdateFireware(const QByteArray& rawBin)
{
    if(m_IsUpdate || !m_IsOpen) return;

    mFireware = rawBin;
    mFirewareLen = mFireware.length();
    m_IsUpdate = true;
    mUpdateState = 0;
    fireSendState = 0;

    if(m_IsOpen) {
        m_Port->write("update\r\n", 8);
        writeLog("SND", "update start");
    }
}

void SerialThread::SlotUpdateStop()
{
    fireSendState = 0;
    m_IsUpdate = false;
    mUpdateState = 0;

    fireRecvLen = 0;
    overTick = 0;
    overNum = 0;
}

void SerialThread::SlotReset()
{
    if(m_IsUpdate || !m_IsOpen) return;

    write((const unsigned char*)"reset\r\n", 7);
}

void SerialThread::UpdateTips(const char* tips, uint8_t flag)
{
    QString tmp = tips;

    fireSendState = 0;
    mUpdateState = false;

    m_IsUpdate = false;

    emit SignalUpdateTips(tmp, flag);
}

QString SerialThread::byteArrayToHexStr(const QByteArray &data)
{
    QString temp = "";
    QString hex = data.toHex();

    for (int i = 0; i < hex.length(); i = i + 2) {
        temp += hex.mid(i, 2) + " ";
    }

    return temp.trimmed().toUpper();
}

void SerialThread::writeLog(const char *title, const unsigned char *buf, int len)
{
    if(len < 4) return;

    QString tmp1;
    bool flag = false;
    bool isChinese = false;

    for(int i = 0; i < len - 1; i++)
    {
        if((buf[i] == '\r' && buf[i+1] == '\n')
            || (buf[i] == 'A' && buf[i+1] == 'T')) {
            flag = true;
        }
        if((buf[i]&0x80) && (buf[i+1]&0x80)) {
            isChinese = true;
            flag = true;
            break;
        }
    }

    if(buf[0] == '\r' && buf[1] == '\n') {
        memmove((void *)buf, (void *)(buf+2), len - 2);
    }

    if(!flag) {
        QByteArray tmp = QByteArray((char *)buf, len);
        tmp1 = byteArrayToHexStr(tmp);
    }
    else {
        //tmp1 = tmp1.replace("\r\n", "");
        if(isChinese) {
            QTextCodec *tc = QTextCodec::codecForName("GBK");
            tmp1 = tc->toUnicode((char *)buf);
        }
        else {
            tmp1 = QString((char *)buf);
        }
        if(buf[len - 1] == '\n' && buf[len - 2] == '\n') {
            tmp1[len - 1] = 0;
            tmp1[len - 2] = 0;
        }
    }
    QString tmp2 = QString("%1:%2").arg(title).arg(tmp1);

    emit SignalSerialLog(tmp2);
}

void SerialThread::writeLog(const char *logTitle, const char *tips)
{
    QString tmp2 = QString("%1:%2").arg(logTitle).arg(tips);

    emit SignalSerialLog(tmp2);
}

void SerialThread::writeLog(const QString &ss)
{
    emit SignalSerialLog(ss);
}
